﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using EnumsNET;
using SharpDX;
using static System.Math;

namespace WindLoad
{
    public class MctModel
    {
        public Dictionary<int, Node> NodeDict { get; private set; }
        public Dictionary<int, Elem> ElemDict { get; private set; }
        public Dictionary<string, Group> GroupDict { get; private set; }
        public Dictionary<string, LoadGroup> LoadGroupDict { get; private set; }
        public Dictionary<string, StaticLoadCase> StaticLoadCaseDict { get; private set; }

        public void ReadMctFileData(string mctFilePath)
        {
            Debug.Assert(!string.IsNullOrWhiteSpace(mctFilePath) &&
                         File.Exists(mctFilePath));

            var allLines = File.ReadAllLines(mctFilePath, Encoding.GetEncoding("gb2312"));
            Debug.Assert(allLines != null && allLines.Length > 0);

            var keywordBlocks = new List<KeywordBlock>();
            for (var i = 0; i < allLines.Length; i++)
            {
                var line = allLines[i].Trim();
                if (!line.StartsWith("*")) // not keyword
                    continue;

                var tokens = line.LeftOf(";").Split(',');
                var keywordBlock = new KeywordBlock
                {
                    Keyword = tokens[0].Trim(),
                    Options = new List<string>(tokens.Skip(1)), // Skip the keyword token
                    DataLines = new List<string>()
                };

                while (i + 1 < allLines.Length)
                {
                    var nextLine = allLines[i + 1].Trim();
                    if (nextLine.StartsWith("*")) // next keyword
                        break;

                    i++;
                    if (string.IsNullOrWhiteSpace(nextLine) || nextLine.StartsWith(";"))
                        continue;

                    keywordBlock.DataLines.Add(nextLine);
                }

                keywordBlocks.Add(keywordBlock);
            }

            //
            // Parse keyword blocks
            //
            NodeDict = keywordBlocks.First(kb => kb.Keyword.EqualTo(Node.Keyword)).DataLines
                .Select(Node.FromDataLine)
                .ToDictionary(item => item.Id, item => item);

            ElemDict = keywordBlocks.First(kb => kb.Keyword.EqualTo(Elem.Keyword)).DataLines
                .Select(Elem.FromDataLine)
                .ToDictionary(item => item.Id, item => item);

            GroupDict = Group.ConcatDataLines(keywordBlocks.First(kb => kb.Keyword.EqualTo(Group.Keyword)).DataLines)
                .Select(Group.FromDataLine)
                .ToDictionary(item => item.Name, item => item);

            StaticLoadCaseDict = keywordBlocks.First(kb => kb.Keyword.EqualTo(StaticLoadCase.Keyword)).DataLines
                .Select(StaticLoadCase.FromDataLine)
                .ToDictionary(item => item.Name, item => item);

            LoadGroupDict = keywordBlocks.First(kb => kb.Keyword.EqualTo(LoadGroup.Keyword)).DataLines
                .Select(LoadGroup.FromDataLine)
                .ToDictionary(item => item.Name, item => item);
        }
    }

    public class KeywordBlock
    {
        public string Keyword { get; set; }
        public List<string> Options { get; set; }
        public List<string> DataLines { get; set; }
    }

    public class Node
    {
        public static readonly string Keyword = "*NODE";
        public int Id { get; set; }
        public double X { get; set; }
        public double Y { get; set; }
        public double Z { get; set; }

        public double DistTo(Node other)
            => Sqrt(Pow(X - other.X, 2) + Pow(Y - other.Y, 2) + Pow(Z - other.Z, 2));

        public Vector3 ToVector3() => new Vector3((float) X, (float) Y, (float) Z);

        public static Node FromDataLine(string dataLine)
        {
            var tokens = dataLine.Split(',');
            return new Node
            {
                Id = int.Parse(tokens[0].Trim()),
                X = double.Parse(tokens[1].Trim()),
                Y = double.Parse(tokens[2].Trim()),
                Z = double.Parse(tokens[3].Trim()),
            };
        }
    }

    public class Elem
    {
        public static readonly string Keyword = "*ELEMENT";
        public int Id { get; set; }
        public List<int> NodeIds { get; set; }

        public static Elem FromDataLine(string dataLine)
        {
            var tokens = dataLine.Split(',');
            return new Elem
            {
                Id = int.Parse(tokens[0].Trim()),
                NodeIds = new List<int>
                {
                    int.Parse(tokens[4].Trim()),
                    int.Parse(tokens[5].Trim()),
                }
            };
        }
    }

    public class Group
    {
        public static readonly string Keyword = "*GROUP";
        public string Name { get; set; }
        public List<int> NodeIds { get; set; }
        public List<int> ElemIds { get; set; }

        public string NodeIdsExpression { get; set; }
        public string ElemIdsExpression { get; set; }

        public static Group FromDataLine(string dataLine)
        {
            var tokens = dataLine.Split(',');
            var nodeIdsExpression = tokens[1].Trim();
            var elemIdsExpression = tokens[2].Trim();
            return new Group
            {
                Name = tokens[0].Trim(),
                NodeIds = IdsHelper.ParseIdsDataLine(nodeIdsExpression),
                ElemIds = IdsHelper.ParseIdsDataLine(elemIdsExpression),
                NodeIdsExpression = nodeIdsExpression,
                ElemIdsExpression = elemIdsExpression,
            };
        }

        public static List<string> ConcatDataLines(IList<string> dataLines)
        {
            var newDataLines = new List<string>();
            var sb = new StringBuilder();
            foreach (var dataLine in dataLines)
            {
                if (dataLine.EndsWith("\\"))
                {
                    sb.Append(dataLine.Left(dataLine.Length - 1));
                    continue;
                }

                sb.Append(dataLine);
                newDataLines.Add(sb.ToString());
                sb.Clear();
            }

            return newDataLines;
        }
    }

    public class LoadGroup
    {
        public static readonly string Keyword = "*LOAD-GROUP";
        public string Name { get; set; }

        public static LoadGroup FromDataLine(string dataLine)
        {
            return new LoadGroup
            {
                Name = dataLine
            };
        }
    }

    public enum LoadCaseType
    {
        User,
        Dead,
        Wind,
    }

    public static class LoadCaseTypeExtension
    {
        public static string Name(this LoadCaseType loadCaseType) => LookUpItems[loadCaseType].Name;

        public static readonly Dictionary<LoadCaseType, Item> LookUpItems = new Dictionary<LoadCaseType, Item>
        {
            {LoadCaseType.User, new Item {Name = "USER"}},
            {LoadCaseType.Dead, new Item {Name = "D"}},
            {LoadCaseType.Wind, new Item {Name = "W"}},
        };

        public class Item
        {
            public string Name { get; set; }
        }
    }

    public class StaticLoadCase
    {
        public static readonly string Keyword = "*STLDCASE";
        public string Name { get; set; }
        public LoadCaseType Type { get; set; }

        public List<PretensionLoad> PretensionLoads { get; set; } = new List<PretensionLoad>();
        public List<NodeLoad> NodeLoads { get; set; } = new List<NodeLoad>();

        public static StaticLoadCase FromDataLine(string dataLine)
        {
            var tokens = dataLine.Split(',');
            var codeName = tokens[1].Trim();
            return new StaticLoadCase
            {
                Name = tokens[0].Trim(),
                Type = LoadCaseTypeExtension.LookUpItems.First(item => item.Value.Name.EqualTo(codeName)).Key
            };
        }

        /// <summary>
        /// 定义工况名称
        /// </summary>
        /// <returns></returns>
        public string ToStaticLoadCaseDataLine()
            => $"{Name}, {Type.Name()},";

        /// <summary>
        /// 定义工况荷载项
        /// </summary>
        /// <returns></returns>
        public List<string> ToUseStaticLoadKeywordBlocks()
        {
            var outputLines = new List<string> {$"*USE-STLD, {Name}"};
            if (PretensionLoads.Any())
                outputLines.AddRange(PretensionLoad.ToKeywordBlock(PretensionLoads));

            if (NodeLoads.Any())
                outputLines.AddRange(NodeLoad.ToKeywordBlock(NodeLoads));

            return outputLines;
        }
    }

    public class NodeLoad
    {
        public static readonly string Keyword = "*CONLOAD";
        public int NodeId { get; set; }
        public double Fx { get; set; }
        public double Fy { get; set; }
        public double Fz { get; set; }
        public double Mx { get; set; }
        public double My { get; set; }
        public double Mz { get; set; }
        public string GroupName { get; set; } = "";

        public string ToDataLine()
            => $"{NodeId}, {Fx:F0}, {Fy:F0}, {Fz:F0}, {Mx:F0}, {My:F0}, {Mz:F0}, {GroupName}";

        public static List<string> ToKeywordBlock(IEnumerable<NodeLoad> loads)
            => new[] {Keyword}
                .Concat(loads.Select(load => load.ToDataLine())).ToList();
    }

    public class PretensionLoad
    {
        public static readonly string Keyword = "*PRETENSION";
        public int ElemId { get; set; }
        public double Pretension { get; set; }
        public string GroupName { get; set; } = "";

        public string ToDataLine()
            => $"{ElemId}, {Pretension}, {GroupName}";

        public static List<string> ToKeywordBlock(IEnumerable<PretensionLoad> loads)
            => new[] {Keyword}
                .Concat(loads.Select(load => load.ToDataLine())).ToList();
    }

    public static class IdsHelper
    {
        private const string Pattern = @"^([1-9]\d*)(?:(?:to?([1-9]\d*)by([1-9]\d*))|(?:to?([1-9]\d*)))?$";
        private static readonly Regex Regex = new Regex(Pattern, RegexOptions.IgnoreCase);

        private static (int IdFrom, int IdTo, int IncrBy) ParseIdsExpression(string idsExpression)
        {
            Debug.Assert(!string.IsNullOrWhiteSpace(idsExpression));
            var idFrom = 0;
            var idTo = 0;
            var incrBy = 1;

            var match = Regex.Match(idsExpression);
            Debug.Assert(match.Success);
            if (!match.Success)
                return (idFrom, idTo, incrBy);

            var groups = match.Groups;
            var v1 = groups[1].Value;
            Debug.Assert(groups.Count == 5 && !string.IsNullOrWhiteSpace(v1));
            idFrom = int.Parse(v1);
            idTo = idFrom;
            Debug.Assert(idFrom > 0);

            var v2 = groups[2].Value;
            var v4 = groups[4].Value;
            if (!string.IsNullOrWhiteSpace(v4)) // with only "to"
            {
                idTo = int.Parse(v4);
                Debug.Assert(idTo >= idFrom);
            }
            else if (!string.IsNullOrWhiteSpace(v2)) // with "to" and "by"
            {
                idTo = int.Parse(v2);
                Debug.Assert(idTo >= idFrom);

                var v3 = groups[3].Value;
                Debug.Assert(!string.IsNullOrWhiteSpace(v3));
                incrBy = int.Parse(v3);
                Debug.Assert(incrBy > 0);
            }

            return (idFrom, idTo, incrBy);
        }

        public static List<int> ParseIdsDataLine(string idsDataLine)
        {
            var tokens = idsDataLine.Split(new[] {' ', ','}, StringSplitOptions.RemoveEmptyEntries)
                .Select(s => s.Trim());
            var ids = new List<int>();
            foreach (var token in tokens)
            {
                var (idFrom, idTo, incrBy) = ParseIdsExpression(token);
                if (idFrom <= 0)
                    continue;

                Debug.Assert(idTo >= idFrom && incrBy > 0);
                var id = idFrom;
                do
                {
                    ids.Add(id);
                    id += incrBy;
                } while (id <= idTo);
            }

            return ids;
        }
    }
}